#!/usr/bin/env python3
"""
Hyperliquid Spot Connector for Hummingbot

This connector implements the Hummingbot connector interface for Hyperliquid spot trading.
"""

import asyncio
import logging
from decimal import Decimal
from typing import Dict, List, Optional, Any
from urllib.parse import urljoin

import aiohttp
from framework.strategy_base import OrderType, TradeType, LimitOrder, OrderFilledEvent, OrderFailedEvent, OrderBook
from framework.connector_base import ConnectorBase, NetworkStatus, TradingRule, InFlightOrder, TradeFeeBase

from config.settings import Config
from utils.hyperliquid_api import HyperliquidAPIClient


def safe_ensure_future(coro):
    """Safely create an asyncio task"""
    return asyncio.create_task(coro)


class HyperliquidSpotConnector(ConnectorBase):
    """
    Hyperliquid Spot Connector implementing Hummingbot's ExchangeBase interface
    """
    
    def __init__(self, trading_pairs: List[str], config: Config):
        """Initialize the Hyperliquid spot connector"""
        super().__init__(trading_pairs)
        
        self._config = config
        self._logger = logging.getLogger(__name__)
        self._api = HyperliquidAPIClient(
            api_key=config.API_WALLET_PRIVATE_KEY,
            api_secret=config.API_WALLET_ADDRESS,
            testnet=config.TEST_MODE
        )
    
    @property
    def name(self) -> str:
        """Exchange name"""
        return "hyperliquid_spot"
    
    @property
    def display_name(self) -> str:
        """Display name for the exchange"""
        return "Hyperliquid Spot"
    
    @property
    def domain(self) -> str:
        """Domain identifier"""
        return self._domain
    
    @property
    def trading_pairs(self) -> List[str]:
        """List of trading pairs"""
        return self._trading_pairs
    
    @property
    def trading_rules(self) -> Dict[str, TradingRule]:
        """Trading rules for each pair"""
        return self._trading_rules
    
    @property
    def in_flight_orders(self) -> Dict[str, InFlightOrder]:
        """In-flight orders"""
        return self._in_flight_orders
    
    @property
    def status_dict(self) -> Dict[str, bool]:
        """Status dictionary"""
        return {
            "account_balance": len(self._account_balances) > 0,
            "trading_rule_initialized": len(self._trading_rules) > 0,
            "order_books_initialized": len(self._order_books) > 0,
        }
    
    @property
    def ready(self) -> bool:
        """Check if connector is ready"""
        return all(self.status_dict.values())
    
    def _start_background_tasks(self):
        """Start background tasks"""
        safe_ensure_future(self._update_trading_rules())
        safe_ensure_future(self._update_balances())
        safe_ensure_future(self._update_order_status())
    
    async def _update_trading_rules(self):
        """Update trading rules from exchange"""
        try:
            # Get trading rules from Hyperliquid API
            async with aiohttp.ClientSession() as session:
                async with session.post(self._info_url, json={"type": "meta"}) as response:
                    if response.status == 200:
                        data = await response.json()
                        universe = data.get("universe", [])
                        
                        for asset_info in universe:
                            symbol = asset_info.get("name")
                            if symbol:
                                # Create trading rule
                                trading_rule = TradingRule(
                                    trading_pair=symbol,
                                    min_order_size=Decimal("0.001"),  # Default minimum
                                    max_order_size=Decimal("1000000"),  # Default maximum
                                    has_margin=False,
                                    buy_order_collateral_token=symbol.split("/")[1] if "/" in symbol else "USDC",
                                    sell_order_collateral_token=symbol.split("/")[0] if "/" in symbol else symbol,
                                )
                                self._trading_rules[symbol] = trading_rule
                        
                        self._logger.info(f"Updated trading rules for {len(self._trading_rules)} pairs")
                    else:
                        self._logger.error(f"Failed to fetch trading rules: {response.status}")
        except Exception as e:
            self._logger.error(f"Error updating trading rules: {e}")
    
    async def _update_balances(self):
        """Update account balances"""
        try:
            # Get balances from Hyperliquid API
            user_address = self._config.HYPERLIQUID_WALLET_ADDRESS
            if not user_address:
                self._logger.warning("No wallet address configured")
                return
            
            async with aiohttp.ClientSession() as session:
                payload = {
                    "type": "clearinghouseState",
                    "user": user_address
                }
                async with session.post(self._info_url, json=payload) as response:
                    if response.status == 200:
                        data = await response.json()
                        balances = data.get("balances", [])
                        
                        # Update account balances
                        for balance_info in balances:
                            coin = balance_info.get("coin")
                            total = Decimal(balance_info.get("total", "0"))
                            hold = Decimal(balance_info.get("hold", "0"))
                            
                            if coin:
                                self._account_balances[coin] = total
                                self._account_available_balances[coin] = total - hold
                        
                        self._logger.info(f"Updated balances for {len(balances)} assets")
                    else:
                        self._logger.error(f"Failed to fetch balances: {response.status}")
        except Exception as e:
            self._logger.error(f"Error updating balances: {e}")
    
    async def _update_order_status(self):
        """Update order status"""
        try:
            # Update status of in-flight orders
            for order_id, order in list(self._in_flight_orders.items()):
                # Check order status via API
                # Implementation depends on Hyperliquid's order status API
                pass
        except Exception as e:
            self._logger.error(f"Error updating order status: {e}")
    
    async def start_network(self):
        """Start network connections"""
        try:
            self._rest_assistant = await self._web_assistants_factory.get_rest_assistant()
            self._network_status = NetworkStatus.CONNECTED
            self._logger.info("Network started successfully")
        except Exception as e:
            self._logger.error(f"Failed to start network: {e}")
            self._network_status = NetworkStatus.NOT_CONNECTED
    
    async def stop_network(self):
        """Stop network connections"""
        self._network_status = NetworkStatus.NOT_CONNECTED
        self._logger.info("Network stopped")
    
    async def check_network(self) -> NetworkStatus:
        """Check network status"""
        try:
            # Ping the API to check connectivity
            async with aiohttp.ClientSession() as session:
                async with session.post(self._info_url, json={"type": "meta"}) as response:
                    if response.status == 200:
                        self._network_status = NetworkStatus.CONNECTED
                    else:
                        self._network_status = NetworkStatus.NOT_CONNECTED
        except Exception:
            self._network_status = NetworkStatus.NOT_CONNECTED
        
        return self._network_status
    
    async def check_network_status(self) -> NetworkStatus:
        """Check network status (abstract method implementation)"""
        return await self.check_network()
    
    async def update_trading_rules(self):
        """Update trading rules (abstract method implementation)"""
        await self._update_trading_rules()
    
    async def update_balances(self):
        """Update balances (abstract method implementation)"""
        await self._update_balances()
    
    async def get_order_status(self, order_id: str) -> Dict[str, Any]:
        """Get order status (abstract method implementation)"""
        # Implementation for getting order status
        try:
            # This would typically call the API to get order status
            return {
                "order_id": order_id,
                "status": "unknown",
                "filled_amount": Decimal("0"),
                "remaining_amount": Decimal("0")
            }
        except Exception as e:
            self._logger.error(f"Error getting order status: {e}")
            return {}
    
    def get_order_book(self, trading_pair: str) -> OrderBook:
        """Get order book for trading pair"""
        return self._order_books.get(trading_pair)
    
    def get_fee(self,
                base_currency: str,
                quote_currency: str,
                order_type: OrderType,
                order_side: TradeType,
                amount: Decimal,
                price: Decimal = Decimal("NaN"),
                is_maker: Optional[bool] = None) -> TradeFeeBase:
        """Get trading fee"""
        # Hyperliquid typically charges 0.02% maker, 0.05% taker
        fee_percent = Decimal("0.0002") if is_maker else Decimal("0.0005")
        return TokenAmount(quote_currency, amount * price * fee_percent)
    
    async def place_order(self,
                         order_id: str,
                         trading_pair: str,
                         amount: Decimal,
                         trade_type: TradeType,
                         order_type: OrderType,
                         price: Decimal,
                         **kwargs) -> str:
        """Place an order"""
        try:
            # Create order payload for Hyperliquid API
            order_payload = {
                "type": "order",
                "orders": [{
                    "a": int(amount * 1000000),  # Amount in micro units
                    "b": trade_type == TradeType.BUY,
                    "p": str(price),
                    "s": trading_pair,
                    "r": False,  # Reduce only
                    "t": {"limit": {"tif": "Gtc"}} if order_type == OrderType.LIMIT else {"market": {}}
                }],
                "grouping": "na"
            }
            
            # Sign and send order
            # Implementation depends on Hyperliquid's authentication
            
            # Create in-flight order
            in_flight_order = InFlightOrder(
                client_order_id=order_id,
                exchange_order_id=None,  # Will be updated when response received
                trading_pair=trading_pair,
                order_type=order_type,
                trade_type=trade_type,
                amount=amount,
                price=price,
                creation_timestamp=self.current_timestamp
            )
            
            self._in_flight_orders[order_id] = in_flight_order
            
            self._logger.info(f"Placed order {order_id} for {amount} {trading_pair} at {price}")
            return order_id
            
        except Exception as e:
            self._logger.error(f"Failed to place order {order_id}: {e}")
            raise
    
    async def cancel_order(self, order_id: str) -> str:
        """Cancel an order"""
        try:
            if order_id in self._in_flight_orders:
                order = self._in_flight_orders[order_id]
                
                # Cancel order via API
                cancel_payload = {
                    "type": "cancel",
                    "cancels": [{
                        "a": int(order.amount * 1000000),
                        "s": order.trading_pair,
                        "o": order.exchange_order_id
                    }]
                }
                
                # Send cancel request
                # Implementation depends on Hyperliquid's API
                
                self._logger.info(f"Cancelled order {order_id}")
                return order_id
            else:
                self._logger.warning(f"Order {order_id} not found for cancellation")
                return order_id
                
        except Exception as e:
            self._logger.error(f"Failed to cancel order {order_id}: {e}")
            raise
    
    def tick(self, timestamp: float):
        """Process periodic updates"""
        # Update order books, balances, etc.
        pass
    
    def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal:
        """Get price quantum for order"""
        return Decimal("0.01")  # Default price step
    
    def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal:
        """Get size quantum for order"""
        return Decimal("0.001")  # Default size step
    
    def quantize_order_amount(self, trading_pair: str, amount: Decimal) -> Decimal:
        """Quantize order amount"""
        quantum = self.get_order_size_quantum(trading_pair, amount)
        return (amount // quantum) * quantum